

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.experimental.UtilityClass;
import lombok.val;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.streamquery.stream.core.collection.Lists;
import org.dromara.streamquery.stream.core.collection.Maps;
import org.dromara.streamquery.stream.core.collection.Sets;
import org.dromara.streamquery.stream.core.optional.Opp;
import org.dromara.streamquery.stream.core.stream.Steam;
import org.dromara.streamquery.stream.plugin.mybatisplus.Database;
import org.dromara.streamquery.stream.plugin.mybatisplus.OneToMany;
import org.dromara.streamquery.stream.plugin.mybatisplus.WrapperHelper;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

import static java.util.Comparator.*;
import static org.dromara.hutool.core.text.CharSequenceUtil.contains;
import static org.dromara.streamquery.stream.core.lambda.function.SerFunc.entryFunc;
import static org.dromara.streamquery.stream.core.stream.collector.Collective.entryToMap;
import static org.dromara.streamquery.stream.core.stream.collector.Collective.groupingBy;

/**
 * @author VampireAchao
 * @since 2022/12/1 10:46
 */
@UtilityClass
public class DetailUtil {

    private static final IProductDetailService PRODUCT_DETAIL_SERVICE = SpringContextHolder.getBean(IProductDetailService.class);
    public static final String BRACKET_PATTERN = "（[^()]*）";

    public static List<String> findFormValues(ProductDetailVO detailVO) {
        final Steam<ProductDetailCardVO> cardSteam = Opp.of(detailVO)
                .map(ProductDetailVO::getCardList).map(Steam::of).orElseGet(Steam::empty);
        return cardSteam.flat(ProductDetailCardVO::getDataList)
                .flat(ProductDetailSectionVO::getFormValues).map(ProductDetailFormVO::getValue).toList();
    }

    public static String findFormMatchValue(ProductDetailVO detailVO) {
        return Opp.ofColl(findFormValues(detailVO)).map(l -> l.get(0)).get();
    }

    public static BigDecimal findCalcValue(ProductDetailVO detailVO) {
        return Opp.of(findFormMatchValue(detailVO))
                .flatMap(val -> Opp.ofTry(() -> new BigDecimal(val))).orElse(null);
    }

    public static Map<Integer, Map<String, String>>
    getChapterIdDetailTitleValueMap(List<Integer> chapterIds, List<String> titlesToLike) {
        if (CollUtil.isEmpty(chapterIds) || CollUtil.isEmpty(titlesToLike)) {
            return Maps.empty();
        }
        val chapterIdTitleDetailMap = getTypeRelationIdTitleDetailMap(Lists.empty(),
                chapterIds, titlesToLike)
                .getOrDefault(ProductDetailTypeEnum.CHAPTER, Maps.empty());
        final Map<Integer, Map<String, String>> chapterIdDetailTitleValueMap =
                Steam.of(chapterIdTitleDetailMap).map(entryFunc((chapterId, titleDetailMap) ->
                        Maps.entry(chapterId, Steam.of(titleDetailMap).map(entryFunc((title, detail) -> {
                            if (Objects.nonNull(title)) {
                                title = title.replaceAll(BRACKET_PATTERN, "").trim();
                            }
                            return Maps.entry(title, DetailUtil.findFormMatchValue(detail));
                        })).collect(entryToMap())))).collect(entryToMap());
        return chapterIdDetailTitleValueMap;
    }

    public static <T> T getDetailByTitle(Map<String, T> titleDetailMap, String detailTitle) {
        if (CollUtil.isEmpty(titleDetailMap)) {
            return null;
        }
        final T detail = titleDetailMap.get(detailTitle);
        if (detail != null) {
            return detail;
        }
        final String title = Steam.of(titleDetailMap.keySet())
                .findFirst(t -> contains(t, detailTitle) ||
                        contains(detailTitle, t)).orElse(detailTitle);
        return titleDetailMap.get(title);
    }

    public static void saveDepart(List<ProductDetailVO> vos) {
        MpUtil.saveRelation(new RelationBO<ProductDetailVO, Long, SysDepartVO, String, DepartProductDetail>() {{
            setMainList(vos);
            setMainKey(ProductDetailVO::getId);
            setAttachKey(SysDepartVO::getDepartId);
            setAttachGetter(ProductDetailVO::getDepartments);
            setRelationMain(DepartProductDetail::getDetailId);
            setRelationAttach(DepartProductDetail::getDepartId);
        }}).execute();
    }

    public static void savePosition(List<ProductDetailVO> vos) {
        MpUtil.saveRelation(new RelationBO<ProductDetailVO, Long, PositionVO, String, PositionProductDetail>() {{
            setMainList(vos);
            setMainKey(ProductDetailVO::getId);
            setAttachKey(PositionVO::getPositionId);
            setAttachGetter(ProductDetailVO::getPositions);
            setRelationMain(PositionProductDetail::getDetailId);
            setRelationAttach(PositionProductDetail::getPositionId);
            setAttachCompares(Lists.of(PositionVO::getType));
            setRelationCompares(Lists.of(PositionProductDetail::getType));
        }}).execute();
    }

    public static void saveStage(List<ProductDetailVO> vos) {
        MpUtil.saveRelation(new RelationBO<ProductDetailVO, Long, DetailStageVO, Integer, StageProductDetail>() {{
            setMainList(vos);
            setMainKey(ProductDetailVO::getId);
            setAttachKey(DetailStageVO::getStageId);
            setAttachGetter(ProductDetailVO::getStages);
            setRelationMain(StageProductDetail::getDetailId);
            setRelationAttach(StageProductDetail::getStageId);
            setAttachCompares(Lists.of(DetailStageVO::getType));
            setRelationCompares(Lists.of(StageProductDetail::getType));
        }}).execute();
    }

    public static boolean isRequire(ProductDetailVO detail) {
        if (Objects.isNull(detail)) {
            return false;
        }
        return Steam.of(detail.getCardList()).flat(ProductDetailCardVO::getDataList)
                .anyMatch(section -> YesOrNoEnum.YES.equals(section.getValueRequired()));
    }

    public static Map<ProductDetailTypeEnum, Map<Integer, Map<String, ProductDetailVO>>>
    getTypeRelationIdTitleDetailMap(List<Integer> productIds,
                                    List<Integer> chapterIds,
                                    List<String> titlesToLike) {
        if (Lists.isEmpty(productIds) && Lists.isEmpty(chapterIds)) {
            return Maps.empty();
        }
        val detailWrapper = Wrappers.lambdaQuery(ProductDetail.class)
                .eq(ProductDetail::getDomain, DetailDomainEnum.PRODUCT_DETAIL)
                .or().eq(ProductDetail::getDomain, DetailDomainEnum.TASK_DETAIL)
                .orderByAsc(ProductDetail::getSort);
        if (Lists.isNotEmpty(titlesToLike)) {
            WrapperHelper.multi(detailWrapper, titlesToLike,
                    (wrap, title) -> wrap.or().like(ProductDetail::getTitle, title));
        }
        val details = ConvertUtil.convertList(
                Database.list(detailWrapper),
                ProductDetailVO.class);
        PRODUCT_DETAIL_SERVICE.resolveAdditional(details, new ProductDetailDTO());
        val idDetailMap = Steam.of(details).toMap(ProductDetailVO::getId);
        val typeRelationIdDetailIdSectionIdFormValuesMap = OneToMany
                .of(ProductDetailForm::getType)
                .condition(wrapper -> wrapper.isNotNull(ProductDetailForm::getValue)
                        .ne(ProductDetailForm::getValue, "")
                        .nested(wrap -> wrap.nested(Lists.isNotEmpty(productIds),
                                        w -> w
                                                .eq(ProductDetailForm::getType, ProductDetailTypeEnum.PRODUCT)
                                                .in(ProductDetailForm::getRelationId, Sets.ofColl(productIds)))
                                .or(Lists.isNotEmpty(chapterIds),
                                        w -> w
                                                .eq(ProductDetailForm::getType, ProductDetailTypeEnum.CHAPTER)
                                                .in(ProductDetailForm::getRelationId,
                                                        Sets.ofColl(chapterIds)))))
                .value(new ProductDetailFormVO().converter())
                .query(HashMap::new, groupingBy(ProductDetailForm::getRelationId,
                        groupingBy(ProductDetailForm::getDetailId,
                                groupingBy(ProductDetailForm::getSectionId))));
        return Steam.of(typeRelationIdDetailIdSectionIdFormValuesMap)
                .map(entryFunc((type, relationIdDetailIdSectionIdFormValuesMap) ->
                        Maps.entry(type, Steam.of(relationIdDetailIdSectionIdFormValuesMap)
                                .map(entryFunc((relationId, detailIdSectionIdFormValuesMap) ->
                                        Maps.entry(relationId, Steam.of(detailIdSectionIdFormValuesMap)
                                                .map(entryFunc((detailId, sectionIdFormValuesMap) -> {
                                                    val detail =
                                                            JacksonUtil.clone(idDetailMap.get(detailId));
                                                    if (Objects.isNull(detail)) {
                                                        return null;
                                                    }
                                                    Steam.of(detail.getCardList())
                                                            .forEach(card -> Steam.of(card.getDataList())
                                                                    .forEach(section -> section.setFormValues(
                                                                            sectionIdFormValuesMap
                                                                                    .getOrDefault(section.getId()
                                                                                            , Lists.empty())
                                                                    )));
                                                    return Maps.entry(detail.getTitle(), detail);
                                                })).nonNull().collect(entryToMap()))))
                                .collect(entryToMap()))))
                .collect(entryToMap());
    }

    public static Map<Integer, List<ProductDetailVO>> getChapterIdDetailsMap(List<Integer> chapterIds) {
        val detailWrapper = Wrappers.lambdaQuery(ProductDetail.class)
                .eq(ProductDetail::getDomain, DetailDomainEnum.PRODUCT_DETAIL)
                .or().eq(ProductDetail::getDomain, DetailDomainEnum.TASK_DETAIL);
        val details = ConvertUtil.convertList(
                Database.list(detailWrapper), ProductDetailVO.class);
        PRODUCT_DETAIL_SERVICE.resolveAdditional(details, new ProductDetailDTO() {{
            setEditType(EditTypeEnum.TASK);
        }});
        val relationIdDetailIdSectionIdFormValuesMap =
                OneToMany.of(ProductDetailForm::getRelationId).in(chapterIds)
                        .condition(wrapper -> wrapper.isNotNull(ProductDetailForm::getValue)
                                .ne(ProductDetailForm::getValue, ""))
                        .value(new ProductDetailFormVO().converter())
                        .query(HashMap::new,
                                groupingBy(ProductDetailForm::getDetailId,
                                        groupingBy(ProductDetailForm::getSectionId)));
        return Steam.of(chapterIds).toMap(Function.identity(), chapterId -> {
            val detailIdSectionIdFormValuesMap = relationIdDetailIdSectionIdFormValuesMap
                    .getOrDefault(chapterId, Maps.empty());
            return Steam.of(details).map(detail -> {
                val sectionIdFormValuesMap = detailIdSectionIdFormValuesMap
                        .getOrDefault(detail.getId(), Maps.empty());
                detail = JacksonUtil.clone(detail);
                Steam.of(detail.getCardList()).forEach(card -> Steam.of(card.getDataList())
                        .forEach(section -> section.setFormValues(
                                sectionIdFormValuesMap.getOrDefault(section.getId(), Lists.empty()))));
                return detail;
            }).toList();
        });
    }

    public static void
    appendForm(List<ProductDetailFormVO> formValues,
               Map<ProductDetailTypeEnum, Map<Integer, Map<String, ProductDetailVO>>> typeRelationIdTitleDetaiMap,
               ProductDetailTypeEnum type, Integer relationId, String title, String value) {
        val detailOpt = Steam.of(typeRelationIdTitleDetaiMap.getOrDefault(type, Maps.empty())
                        .getOrDefault(relationId, Maps.empty()).values())
                .sorted(comparing(ProductDetailVO::getSort,
                        nullsLast(naturalOrder())))
                .findFirst(d -> d.getTitle().contains(title));
        if (!detailOpt.isPresent()) {
            return;
        }
        val detail = detailOpt.get();
        if (Lists.isEmpty(detail.getCardList())) {
            return;
        }
        val card = detail.getCardList().get(0);
        if (Lists.isEmpty(card.getDataList())) {
            return;
        }
        val section = card.getDataList().get(0);
        final ProductDetailFormVO formValue;
        if (Lists.isNotEmpty(section.getFormValues())) {
            formValue = section.getFormValues().get(0);
        } else {
            formValue = new ProductDetailFormVO() {{
                setType(type);
                setRelationId(relationId);
                setDetailId(detail.getId());
                setSectionId(section.getId());
            }};
            section.setFormValues(Lists.of(formValue));
        }
        formValue.setValue(value);
        formValues.add(formValue);
    }


    public static void sync(DetailSyncDTO dto) {
        if (dto.getDetailTitleValueMap().isEmpty()) {
            return;
        }
        val typeRelationIdTitleDetailMap =
                DetailUtil.getTypeRelationIdTitleDetailMap(
                        Lists.of(dto.getProductionId()),
                        Lists.of(dto.getChapterId()),
                        Lists.ofColl(dto.getDetailTitleValueMap().keySet()));
        final List<ProductDetailFormVO> formValues = Lists.of();
        dto.getDetailTitleValueMap().forEach((title, detailValue) -> {
            DetailUtil.appendForm(formValues, typeRelationIdTitleDetailMap,
                    ProductDetailTypeEnum.PRODUCT, dto.getProductionId(), title, detailValue);
            DetailUtil.appendForm(formValues, typeRelationIdTitleDetailMap,
                    ProductDetailTypeEnum.CHAPTER, dto.getChapterId(), title, detailValue);
        });
        Database.saveOrUpdateFewSql(formValues);
    }
}
